package com.zuma.oci.docker;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.zuma.oci.docker.configuration.DockerConnectionConfiguration;
import com.zuma.oci.docker.handler.BuildImageLinePrint;
import com.zuma.oci.docker.handler.ImageBuildHandler;
import com.zuma.oci.docker.handler.ImagePushHandler;
import com.zuma.oci.docker.param.ImageBuildParam;
import com.zuma.oci.docker.transport.HttpTransport;
import com.zuma.oci.docker.type.*;
import com.zuma.oci.io.CompressedDirectory;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.io.entity.FileEntity;
import org.apache.hc.core5.net.URIBuilder;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

public class DockerApi {

    static final String API_VERSION_HEADER_NAME = "API-Version";
    /**
     * 调用api的最低版本
     */
    static final ApiVersion API_VERSION = ApiVersion.of(1, 24);
    /**
     * 指定平台的最低版本
     */
    static final ApiVersion PLATFORM_API_VERSION = ApiVersion.of(1, 41);

    static final ApiVersion UNKNOWN_API_VERSION = ApiVersion.of(0, 0);

    private volatile ApiVersion apiVersion = null;

    private final HttpTransport http;

    private final SystemApi system = new SystemApi();

    private final ImageApi image;

    public DockerApi() {
        this((DockerConnectionConfiguration) null);
    }

    public DockerApi(DockerConnectionConfiguration connectionConfiguration) {
        this(HttpTransport.create(connectionConfiguration));
    }

    public DockerApi(HttpTransport http) {
        this.http = http;
        this.image = new ImageApi();
    }

    public ImageApi image() {
        return image;
    }

    private HttpTransport http() {
        return this.http;
    }


    public class SystemApi {
        /**
         * Get the API version supported by the Docker daemon.
         *
         * @return the Docker daemon API version
         */
        ApiVersion getApiVersion() {
            try {
                URI uri = new URIBuilder("/_ping").build();
                try (HttpTransport.Response response = http().head(uri)) {
                    Header apiVersionHeader = response.getHeader(API_VERSION_HEADER_NAME);
                    if (apiVersionHeader != null) {
                        return ApiVersion.parse(apiVersionHeader.getValue());
                    }
                } catch (Exception ex) {
                    // this.log.log("Warning: Failed to determine Docker API version: " + ex.getMessage());
                    // fall through to return default value
                }
                return UNKNOWN_API_VERSION;
            } catch (URISyntaxException ex) {
                throw new IllegalStateException(ex);
            }
        }

    }

    public class ImageApi {
        /**
         * 构建镜像
         *
         * @param param    参数
         * @param dir      docker的上下文路基
         * @param tailFunc 响应处理
         * @return
         * @throws Exception
         */
        public String build(ImageBuildParam param, Map<String, ImageBuildRegistryConfig> authConfig, Path dir, Consumer<BuildImageLinePrint> tailFunc) throws Exception {
            try (var dockerPath = CompressedDirectory.create(dir == null ? Path.of(".") : dir)) {
                var uri = StrUtil.isNotBlank(param.get(ImageBuildParam.PLATFORM)) ? buildUrl(PLATFORM_API_VERSION, "/build", param)
                        : buildUrl("/build", param);
                var entity = new FileEntity(dockerPath.file().toFile(), ContentType.APPLICATION_OCTET_STREAM);
                var requestParam = new RequestParam().setUri(uri).setEntity(entity).setContentType("application/x-tar");
                if (CollectionUtil.isNotEmpty(authConfig)) {
                    var registryConfigStr = Base64.encodeUrlSafe(JSON.toJSONString(authConfig));
                    requestParam.setHeaders(Map.of("X-Registry-Config", registryConfigStr));
                }
                return http.post(requestParam, new ImageBuildHandler(tailFunc));
            } catch (Exception e) {
                e.printStackTrace();
                throw e;
            }
        }

        public void push(String imageName, String platform, RegistryAuth auth) {
            var imageRef = new ImageRef(imageName);
            var param = new HashMap<String, String>();
            var url = StrUtil.format("/images/{}/push", imageRef.getImage());
            if (imageRef.getTag() != null) {
                param.put("tag", imageRef.getTag());
            }
            if (platform != null) {
                param.put("platform", platform);
            }
            var uri = StrUtil.isNotBlank(platform) ? buildUrl(PLATFORM_API_VERSION, url, param)
                    : buildUrl(url, param);
            var requestParam = new RequestParam().setUri(uri).setContentType("application/json");
            if (auth != null) {
                requestParam.setRegistryAuth(auth.toString());
            }
            http.post(requestParam, new ImagePushHandler(message -> {
                if (message.getProgress() != null) {
                    System.out.println(message.getProgress());
                }
                if (message.getStream() != null) {
                    System.out.println(message.getStream());
                }
                if (message.getError() != null) {
                    System.err.println(message.getError());
                }
                if (message.getStatus() != null) {
                    System.out.println(message.getStatus());
                }
            }));
        }
    }


    public static void main(String[] args) throws Exception {
        /*var t = HttpTransport.create((DockerConnectionConfiguration) null) ;
        var param = new HashMap<String, String>();
        param.put("t", "docker:v1.0");
        var queryStr = URLUtil.buildQuery(param, StandardCharsets.UTF_8);
        try (var dockerPath = CompressedDirectory.create(Path.of("./"))) {
            t.test(new HttpPost(new URI("/v1.49/build?" + queryStr)),  "application/x-tar", dockerPath.file());
        } catch (Exception e) {
            e.printStackTrace();
        }*/
        var dockerApi = new DockerApi();
        var param = new ImageBuildParam();
        param.set(ImageBuildParam.T, "docker:v1.0");
       // param.platforms("linux/arm64");
        var config = new ImageBuildRegistryConfig().setUsername("h20171009").setPassword("hooli2020");
        dockerApi.image().build(param, Map.of("registry.cn-beijing.aliyuncs.com", config), null, res -> {
            if (StrUtil.isNotBlank(res.getError())) {
                System.out.println(res.getError());
            }
            if (StrUtil.isNotBlank(res.getStream())) {
                System.out.println(res.getStream());
            }
            if (StrUtil.isNotBlank(res.getStatus())) {
                System.out.println(res.getStatus());
            }
        });
        System.exit(0);
    }

    private URI buildUrl(String path, Map<String, String> params) {
        return buildUrl(API_VERSION, path, params);
    }

    private URI buildUrl(ApiVersion apiVersion, String path, Map<String, String> params) {
        verifyApiVersion(apiVersion);
        try {
            URIBuilder builder = new URIBuilder("/v" + apiVersion + path);
            if (params != null) {
                for (var paramEntry : params.entrySet()) {
                    builder.addParameter(paramEntry.getKey(), paramEntry.getValue());

                }
            }
            return builder.build();
        } catch (URISyntaxException ex) {
            throw new IllegalStateException(ex);
        }
    }

    private void verifyApiVersion(ApiVersion minimumVersion) {
        ApiVersion actualVersion = getApiVersion();
        Assert.state(actualVersion.equals(UNKNOWN_API_VERSION) || actualVersion.supports(minimumVersion),
                () -> "Docker API version must be at least " + minimumVersion
                        + " to support this feature, but current API version is " + actualVersion);
    }

    private ApiVersion getApiVersion() {
        ApiVersion apiVersion = this.apiVersion;
        if (this.apiVersion == null) {
            apiVersion = this.system.getApiVersion();
            this.apiVersion = apiVersion;
        }
        return apiVersion;
    }


}
